package net.keebook.webapp.bean;

import java.io.IOException;

import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.faces.context.FacesContext;
import javax.inject.Inject;

import net.keebook.webapp.dao.SettingDao;
import net.keebook.webapp.model.SessionModel;
import net.keebook.webapp.util.SettingId;
import net.keebook.webapp.util.oauth.Google2Api;
import net.keebook.webapp.util.oauth.OpenIdCredentials;

import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;
import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.DefaultApi20;
import org.scribe.builder.api.FacebookApi;
import org.scribe.builder.api.TwitterApi;
import org.scribe.exceptions.OAuthException;
import org.scribe.model.OAuthConfig;
import org.scribe.model.OAuthConstants;
import org.scribe.model.OAuthRequest;
import org.scribe.model.Response;
import org.scribe.model.Token;
import org.scribe.model.Verb;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

@Singleton
public class OAuthBean {
	@Inject
	private SettingDao settingDao;
	
	private OAuthService serviceFacebook = null;
	private OAuthService serviceTwitter = null;
	private OAuthConfig configGoogle = null;
	private DefaultApi20 apiGoogle = null;
	private OAuthService serviceGoogle = null;
	
	private static final String PROVIDER_FACEBOOK = "facebook";
	private static final String PROVIDER_GOOGLE = "google";
	private static final String PROVIDER_TWITTER = "twitter";

	@PostConstruct
	protected void init() {
		initFacebookService();
		initGoogleService();
		initTwitterService();
	}
	
	private void initFacebookService() {
		if (getSettingDao().get(SettingId.FACEBOOK_LOGIN).getBoolean()) {
			String appKey = getSettingDao().get(SettingId.FACEBOOK_APP_ID).getValue();
			String apiSecret = getSettingDao().get(SettingId.FACEBOOK_APP_SECRET).getValue();
			serviceFacebook = new ServiceBuilder().provider(FacebookApi.class).apiKey(appKey).apiSecret(apiSecret).callback(getCallbackUrl("facebook")).build();
		}
	}
	
	private void initTwitterService() {
		if (getSettingDao().get(SettingId.TWITTER_LOGIN).getBoolean()) {
			String appKey = getSettingDao().get(SettingId.TWITTER_CONSUMER_KEY).getValue();
			String apiSecret = getSettingDao().get(SettingId.TWITTER_CONSUMER_SECRET).getValue();
			serviceTwitter = new ServiceBuilder().provider(TwitterApi.class).apiKey(appKey).apiSecret(apiSecret).callback(getCallbackUrl("twitter")).build();
		}
	}
	
	private void initGoogleService() {
		if (getSettingDao().get(SettingId.GOOGLE_LOGIN).getBoolean()) {
			String appKey = getSettingDao().get(SettingId.GOOGLE_CLIENT_ID).getValue();
			String apiSecret = getSettingDao().get(SettingId.GOOGLE_CLIENT_SECRET).getValue();
			configGoogle = new OAuthConfig(appKey, apiSecret, getCallbackUrl("google"), null, "https://www.googleapis.com/auth/userinfo.profile");
			apiGoogle = new Google2Api();
			serviceGoogle = apiGoogle.createService(configGoogle);
		}
	}
	
	private String getCallbackUrl(String provider) {
		String root = getSettingDao().get(SettingId.PUBLIC_URL).getValue();
		if (!root.endsWith("/")) {
			root += "/";
		}
		String callbackUrl = root + "services/oauth/"+provider;
		return callbackUrl;
	}
	
	public void loginFacebook(FacesContext facesContext) throws IOException {
		OAuthService service = getServiceFacebook();
		String authUrl = service.getAuthorizationUrl(null);
		facesContext.getExternalContext().redirect(authUrl);
	}
	
	public void loginGoogle(FacesContext facesContext) throws IOException {
		OAuthService service = getServiceGoogle();
		String authUrl = service.getAuthorizationUrl(null);
		facesContext.getExternalContext().redirect(authUrl);
	}
	
	public void loginTwitter(FacesContext facesContext, SessionModel sessionModel) throws IOException {
		OAuthService service = getServiceTwitter();
		Token requestToken = service.getRequestToken();
		String authUrl = service.getAuthorizationUrl(requestToken);
		sessionModel.setoAuthRequestToken(requestToken);
		facesContext.getExternalContext().redirect(authUrl);
	}
	
	private String getAuthRequestUrl(String provider) {
		if (PROVIDER_FACEBOOK.equals(provider)) {
			return "https://graph.facebook.com/me";
		}
		if (PROVIDER_TWITTER.equals(provider)) {
			return "http://api.twitter.com/1/account/verify_credentials.json";
		}
		if (PROVIDER_GOOGLE.equals(provider)) {
			return "https://www.googleapis.com/oauth2/v1/tokeninfo"; 
		}
		return "";
	}
	
	public OAuthService getService(String provider) {
		if (PROVIDER_FACEBOOK.equals(provider)) {
			return getServiceFacebook();
		}
		if (PROVIDER_GOOGLE.equals(provider)) {
			return getServiceGoogle();
		}
		if (PROVIDER_TWITTER.equals(provider)) {
			return getServiceTwitter();
		}
		return null;
	}
	
	/**
	 * Google requires the grant_type parameter to be passed in the HTTP POST body.
	 * This is not supported by Scribe, so this workaround is required.
	 */
	private Token getAccessToken (String provider, Verifier verifier, Token requestToken) {
		if (PROVIDER_FACEBOOK.equals(provider)) {
			return getServiceFacebook().getAccessToken(null, verifier);
		}
		if (PROVIDER_TWITTER.equals(provider)) {
			return getServiceTwitter().getAccessToken(requestToken, verifier);
		}
		if (PROVIDER_GOOGLE.equals(provider)) {
			DefaultApi20 api = getApiGoogle();
			OAuthConfig config = getConfigGoogle();
			
			OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
		    request.addBodyParameter(OAuthConstants.CLIENT_ID, config.getApiKey());
		    request.addBodyParameter(OAuthConstants.CLIENT_SECRET, config.getApiSecret());
		    request.addBodyParameter(OAuthConstants.CODE, verifier.getValue());
		    request.addBodyParameter(OAuthConstants.REDIRECT_URI, config.getCallback());
		    request.addBodyParameter("grant_type", "authorization_code");
		    request.addBodyParameter(OAuthConstants.SCOPE, config.getScope());
		    Response response = request.send();
		    return api.getAccessTokenExtractor().extract(response.getBody());
		}
		return null;
	}
	
	public OpenIdCredentials verify(String provider, String code, SessionModel sessionModel) {
		try {
			OAuthService service = getService(provider);
			Verifier verifier = new Verifier(code);
			Token accessToken = getAccessToken(provider, verifier, sessionModel.getoAuthRequestToken());
			
			OAuthRequest request = new OAuthRequest(Verb.GET, getAuthRequestUrl(provider));
			service.signRequest(accessToken, request);
			Response response = request.send();
			
			// Google requires to look up the user information separately
			if (PROVIDER_GOOGLE.equals(provider)) {
				request = new OAuthRequest(Verb.GET, "https://www.googleapis.com/oauth2/v1/userinfo");
				service.signRequest(accessToken, request);
				response = request.send();
			}
			
			try {
				JSONObject responseObject = new JSONObject(response.getBody());
				return buildCredentials(responseObject, provider);
			} catch (JSONException e) {
				e.printStackTrace();
				return null;
			}
		} catch (OAuthException e) {
			Logger.getLogger(this.getClass()).error("Error verifying OAuth Response", e);
			return null;
		}
	}
	
	private OpenIdCredentials buildCredentials(JSONObject responseObject, String provider) throws JSONException {
		OpenIdCredentials credentials = new OpenIdCredentials(provider + "-"+responseObject.getString("id"));
		//credentials.setEmail("");
		if (PROVIDER_FACEBOOK.equals(provider)) {
			credentials.setName(responseObject.has("name") ? responseObject.getString("name") : "");
		} else if (PROVIDER_GOOGLE.equals(provider)) {
			credentials.setName(responseObject.has("name") ? responseObject.getString("name") : "");
		} else if (PROVIDER_TWITTER.equals(provider)) {
			credentials.setName(responseObject.has("name") ? responseObject.getString("name") : "");
		}
		return credentials;
	}

	public SettingDao getSettingDao() {
		return settingDao;
	}

	public void setSettingDao(SettingDao settingDao) {
		this.settingDao = settingDao;
	}

	public OAuthService getServiceFacebook() {
		return serviceFacebook;
	}
	
	public OAuthService getServiceTwitter() {
		return serviceTwitter;
	}
	
	public OAuthService getServiceGoogle() {
		return serviceGoogle;
	}
	
	public DefaultApi20 getApiGoogle() {
		return apiGoogle;
	}
	
	public OAuthConfig getConfigGoogle() {
		return configGoogle;
	}
}
